#![allow(non_snake_case)]
use crate::{Input, InputPrefix, InputSuffix, Scrollbar, ScrollbarRef, _binder::Binder};
use chrono::{Local, NaiveTime, Timelike};
use icondata as i;
use leptos::{ev, html, prelude::*};
use leptos_icons::*;
use shq_common::prelude::Size;
use thaw_components::{Follower, FollowerPlacement};
use thaw_utils::{ArcOneCallback, BoxOneCallback, ComponentRef, Model, SignalWatch};

#[component]
pub fn TimePicker(
    #[prop(optional, into)] value: Model<Option<NaiveTime>>,
    #[prop(optional, into)] on_change: Option<BoxOneCallback<Option<String>>>,
    #[prop(optional, into)] placeholder: MaybeProp<String>,
    #[prop(optional, into)] size: MaybeProp<Size>,
    #[prop(optional)] input_prefix: Option<InputPrefix>,
    #[prop(optional)] input_suffix: Option<InputSuffix>,
    #[prop(optional, into)] disabled: MaybeProp<bool>,
    #[prop(optional)] inner_max_width: u32,
    #[prop(default=true.into(), into)] border: MaybeProp<bool>,
    #[prop(optional)] is_init_focus: bool,
) -> impl IntoView {
    let time_picker_ref = NodeRef::<html::Div>::new();
    let panel_ref = ComponentRef::<PanelRef>::default();
    let is_show_panel = RwSignal::new(false);
    let show_time_format = "%H:%M:%S";
    let show_time_text: RwSignal<Option<String>> = RwSignal::new(None);
    let update_show_time_text = move || {
        value.with_untracked(move |time| {
            let text = time
                .as_ref()
                .map_or(None, |time| Some(time.format(show_time_format).to_string()));
            show_time_text.set(text);
        });
    };
    update_show_time_text();
    let panel_selected_time = RwSignal::new(None::<NaiveTime>);
    _ = panel_selected_time.watch(move |time| {
        let text = time
            .as_ref()
            .map_or(None, |time| Some(time.format(show_time_format).to_string()));
        show_time_text.set(text);
    });

    let on_input_blur = BoxOneCallback::new(move |_| {
        show_time_text.with(|text| {
            if let Some(text) = text {
                if let Ok(time) =
                    NaiveTime::parse_from_str(text, show_time_format)
                {
                    if value.get_untracked() != Some(time) {
                        value.set(Some(time));
                        update_show_time_text();
                    }
                } else {
                    update_show_time_text();
                }
            }else{
                value.set(None);
            }
        });
    });
    let close_panel = ArcOneCallback::new(move |time: Option<NaiveTime>| {
        if value.get_untracked() != time {
            if let Some(on_change)=on_change.as_ref() {
                on_change(time.map(|t|t.format(show_time_format).to_string()));
            }
            value.set(time);
            update_show_time_text();
        }
        is_show_panel.set(false);
    });

    let open_panel = BoxOneCallback::new(move |_| {
        panel_selected_time.set(value.get_untracked());
        is_show_panel.set(true);
        request_animation_frame(move || {
            if let Some(panel_ref) = panel_ref.get_untracked() {
                panel_ref.scroll_into_view();
            }
        });
    });

    view! {
        <Binder>
            <div node_ref=time_picker_ref>
                <Input value=show_time_text on_focus=open_panel on_blur=on_input_blur size disabled placeholder inner_max_width border is_init_focus>
                    <InputPrefix slot>
                        {if let Some(ip) = input_prefix.and_then(|p| p.if_.then_some(p)) {
                            Some((ip.children)())
                        } else {
                            None
                        }}
                    </InputPrefix>
                    <InputSuffix slot>
                        <Icon icon=i::AiClockCircleOutlined style="font-size: 18px"/>
                        {if let Some(sp) = input_suffix.and_then(|s|s.if_.then_some(s)) {
                            Some((sp.children)())
                        } else {
                            None
                        }}
                    </InputSuffix>
                </Input>
            </div>
            <Follower slot show=is_show_panel placement=FollowerPlacement::BottomStart>
                <Panel
                    selected_time=panel_selected_time
                    close_panel
                    time_picker_ref
                    is_show_panel
                    comp_ref=panel_ref
                />
            </Follower>
        </Binder>
    }
}

#[component]
fn Panel(
    selected_time: RwSignal<Option<NaiveTime>>,
    time_picker_ref: NodeRef<html::Div>,
    close_panel: ArcOneCallback<Option<NaiveTime>>,
    #[prop(into)] is_show_panel: MaybeProp<bool>,
    comp_ref: ComponentRef<PanelRef>,
) -> impl IntoView {
    let cp1=close_panel.clone();
    let cp2=close_panel.clone();
    let now = move |_| {
        cp1(Some(now_time()));
    };
    let ok = move |_| {
        cp2(selected_time.get_untracked());
    };

    let panel_ref = NodeRef::<html::Div>::new();
    #[cfg(any(feature = "csr", feature = "hydrate"))]
    {
        use leptos::wasm_bindgen::__rt::IntoJsResult;
        let handle = window_event_listener(ev::click, move |ev| {
            let el = ev.target();
            let mut el: Option<web_sys::Element> =
                el.into_js_result().map_or(None, |el| Some(el.into()));
            let body = document().body().unwrap();
            while let Some(current_el) = el {
                if current_el == *body {
                    break;
                };
                if panel_ref.get().is_none() {
                    return;
                }
                if current_el == **panel_ref.get_untracked().unwrap()
                    || current_el == **time_picker_ref.get_untracked().unwrap()
                {
                    return;
                }
                el = current_el.parent_element();
            }
            close_panel.clone()(None);
        });
        on_cleanup(move || handle.remove());
    }
    // #[cfg(not(any(feature = "csr", feature = "hydrate")))]
    // {
    //     _ = time_picker_ref;
    //     _ = panel_ref;
    // }

    let hour_ref = ComponentRef::<ScrollbarRef>::new();
    let minute_ref = ComponentRef::<ScrollbarRef>::new();
    let second_ref = ComponentRef::<ScrollbarRef>::new();
    comp_ref.load(PanelRef {
        hour_ref,
        minute_ref,
        second_ref,
    });

    view! {
        <div
            class="w-40 bg-base-100 rounded-lg shadow-lg box-border"
            class=("hidden",move||!is_show_panel.get().unwrap_or_default())
            node_ref=panel_ref
        >
            <div class="flex flex-row">
                <div class="flex-1 h-60">
                    <Scrollbar size=6 comp_ref=hour_ref>
                        {(0..24)
                            .map(|hour| {
                                let comp_ref = ComponentRef::<PanelTimeItemRef>::default();
                                let on_click = move |_| {
                                    selected_time
                                        .update(move |time| {
                                            *time = if let Some(time) = time {
                                                time.with_hour(hour)
                                            } else {
                                                NaiveTime::from_hms_opt(hour, 0, 0)
                                            }
                                        });
                                    comp_ref.get_untracked().unwrap().scroll_into_view();
                                };
                                let is_selected = Memo::new(move |_| {
                                    selected_time.get().map_or(false, |v| v.hour() == hour)
                                });
                                view! {
                                    <PanelTimeItem
                                        value=hour
                                        on:click=on_click
                                        is_selected
                                        comp_ref
                                    />
                                }
                            })
                            .collect_view()}
                    </Scrollbar>
                    <div class="h-60"></div>
                </div>
                <div class="flex-1 h-60">
                    <Scrollbar size=6 comp_ref=minute_ref>
                        {(0..60)
                            .map(|minute| {
                                let comp_ref = ComponentRef::<PanelTimeItemRef>::default();
                                let on_click = move |_| {
                                    selected_time
                                        .update(move |time| {
                                            *time = if let Some(time) = time {
                                                time.with_minute(minute)
                                            } else {
                                                NaiveTime::from_hms_opt(now_time().hour(), minute, 0)
                                            }
                                        });
                                    comp_ref.get_untracked().unwrap().scroll_into_view();
                                };
                                let is_selected = Memo::new(move |_| {
                                    selected_time.get().map_or(false, |v| v.minute() == minute)
                                });
                                view! {
                                    <PanelTimeItem
                                        value=minute
                                        on:click=on_click
                                        is_selected
                                        comp_ref
                                    />
                                }
                            })
                            .collect_view()}
                    </Scrollbar>
                    <div class="h-60"></div>
                </div>
                <div class="flex-1 h-60">
                    <Scrollbar size=6 comp_ref=second_ref>
                        {(0..60)
                            .map(|second| {
                                let comp_ref = ComponentRef::<PanelTimeItemRef>::default();
                                let on_click = move |_| {
                                    selected_time
                                        .update(move |time| {
                                            *time = if let Some(time) = time {
                                                time.with_second(second)
                                            } else {
                                                now_time().with_second(second)
                                            }
                                        });
                                    comp_ref.get_untracked().unwrap().scroll_into_view();
                                };
                                let is_selected = Memo::new(move |_| {
                                    selected_time.get().map_or(false, |v| v.second() == second)
                                });
                                view! {
                                    <PanelTimeItem
                                        value=second
                                        on:click=on_click
                                        is_selected
                                        comp_ref
                                    />
                                }
                            })
                            .collect_view()}
                    </Scrollbar>
                    <div class="h-60"></div>
                </div>
            </div>
            <div class="flex justify-center gap-2">
                <button class="btn btn-outline btn-xs" on:click=now>
                    "现在"
                </button>
                <button class="btn btn-outline btn-xs" on:click=ok>
                    "确定"
                </button>
            </div>
        </div>
    }
}

#[derive(Clone)]
struct PanelRef {
    hour_ref: ComponentRef<ScrollbarRef>,
    minute_ref: ComponentRef<ScrollbarRef>,
    second_ref: ComponentRef<ScrollbarRef>,
}

impl PanelRef {
    fn scroll_top(scrollbar_ref: ScrollbarRef) {
        let Some(contetn_ref) = scrollbar_ref.content_ref.get_untracked() else {
            return;
        };
        let Ok(Some(slected_el)) =
            contetn_ref.query_selector(".thaw-time-picker-panel__time-item--slected")
        else {
            return;
        };
        use wasm_bindgen::JsCast;
        if let Ok(slected_el) = slected_el.dyn_into::<web_sys::HtmlElement>() {
            let options = web_sys::ScrollToOptions::new();
            options.set_top(f64::from(slected_el.offset_top()));
            scrollbar_ref.scroll_to_with_scroll_to_options(&options);
        }
    }

    fn scroll_into_view(&self) {
        if let Some(hour) = self.hour_ref.get_untracked() {
            Self::scroll_top(hour);
        }
        if let Some(minute) = self.minute_ref.get_untracked() {
            Self::scroll_top(minute);
        }
        if let Some(second) = self.second_ref.get_untracked() {
            Self::scroll_top(second);
        }
    }
}

#[component]
fn PanelTimeItem(
    value: u32,
    is_selected: Memo<bool>,
    comp_ref: ComponentRef<PanelTimeItemRef>,
) -> impl IntoView {
    let item_ref = NodeRef::new();
    item_ref.on_load(move |_| {
        let item_ref = PanelTimeItemRef { item_ref };
        comp_ref.load(item_ref);
    });
    view! {
        <div
            class="h-9 m-0.5 flex items-center justify-center cursor-pointer rounded bg-base-100 hover:bg-base-300"
            class=("text-info", move || is_selected.get())
            node_ref=item_ref
        >
            {format!("{value:02}")}
        </div>
    }
}

#[derive(Clone)]
struct PanelTimeItemRef {
    item_ref: NodeRef<html::Div>,
}

impl PanelTimeItemRef {
    fn scroll_into_view(&self) {
        if let Some(item_ref) = self.item_ref.get_untracked() {
            item_ref.scroll_into_view_with_bool(true);
        }
    }
}

fn now_time() -> NaiveTime {
    Local::now().time()
}
